# gplugs/projecttracker.py
#
#

""" 
    track hours spent working on projects

    Persitent data is stored in a gozerbot Persist object, 'projects'

    user is owner of a project:
        projects.data[username][project] exists
        contrib.data[username][project] MUST NOT exist

        projects.data[username][project]['hours'] may exist
            dict: {user: total hours, ...}
        projects.data[username][project]['desc'] may exist
            string: project description
        projects.data[username][project]['share'] may exist
            list: other contributing users


    user contributes to a project of another user:
        contrib.data[username][project] exists
            string: project owner username
        projects.data[username][project] MUST NOT exist

"""

__gendocfirst__ = ['pt-add', ]
__gendoclast__ = ['pt-del', ]
__author__ = "Hans van Kranenburg <hans@knorrie.org>"
__status__ = "seen"

## gozerbot imports

from gozerbot.commands import cmnds
from gozerbot.examples import examples
from gozerbot.users import users
from gozerbot.datadir import datadir
from gozerbot.persist.persist import PlugPersist
from gozerbot.plughelp import plughelp
from gozerbot.tests import tests

## basic imports

import os

## plughelp

plughelp.add('projecttracker', 'track hours spent working on projects')

## defines

projects = PlugPersist('pt-projects')
contrib  = PlugPersist('pt-contrib') 
if not projects.data: projects.data = {}
if not contrib.data: contrib.data = {}

## size command

def size():
    """ return number of projects """
    size = 0
    for userprojects in projects.data:
        size += len(userprojects)
    return size

## addproject function

def addproject(username, project, desc):
    """ add project to projects of username """
    if not projects.data.has_key(username):
        projects.data[username] = {}
    if not projects.data[username].has_key(project):
        projects.data[username][project] = {}
    if desc:
        projects.data[username][project]['desc'] = desc
    projects.save()

## hasproject function

def hasproject(username, project):
    """ look if this user owns a project """
    if projects.data.has_key(username) and \
    projects.data[username].has_key(project):
        return True
    return False

## delproject function

def delproject(username, project):
    """ delete a project """
    try:
        if projects.data[username].has_key(project):
            if projects.data[username][project].has_key('share'):
                for otheruser in \
                projects.data[username][project]['share']:
                    try:
                        del contrib.data[otheruser][project]
                    except KeyError:
                        pass
            del projects.data[username][project]
    except KeyError:
        pass
    projects.save()
    contrib.save()

## getproject function

def getprojects(username):
    """ return projects owned by username """
    try:
        return projects.data[username].keys()
    except KeyError:
        return []

## serdesc function

def setdesc(username, project, desc):
    """ alter project description """
    try:
        projects.data[username][project]['desc'] = desc
	projects.save()
    except KeyError:
        pass

## getdesc function

def getdesc(username, project):
    """ return description of project """
    try:
        return projects.data[username][project]['desc']
    except KeyError:
        return None

## addshare function

def addshare(username, project, otheruser):
    """ share our project with another user
    make sure that 1) we own the project and 2) the other user has
    no project with this name and does not contribute to a project
    with the same name before calling this function    
    """
    if not projects.data[username][project].has_key('share'):
        projects.data[username][project]['share'] = [otheruser, ]
    else:
        projects.data[username][project]['share'].append(otheruser)
    projects.save()
    if not contrib.data.has_key(otheruser):
        contrib.data[otheruser] = {project: username}
    else:
        contrib.data[otheruser][project] = username
    contrib.save()

## delshare function

def delshare(username, project, otheruser):
    """ stop sharing our project with another user """
    # remove other user from the sharelist
    try:
        projects.data[username][project]['share'].remove(otheruser)
        projects.save()
    except KeyError:
        pass
    try:
        del contrib.data[otheruser][project]
        contrib.save()
    except KeyError:
        pass

## hascontrib function

def hascontrib(username, project):
    """ does this user contribute to a project with this name? """
    try:
        return contrib.data[username].has_key(project)
    except KeyError:
        return False

## getowner function

def getowner(username, project):
    """ get owner of a project we contribute to """
    if hascontrib(username, project):
        return contrib.data[username][project]
    return None

## getcontrib function

def getcontrib(username):
    """ get names of projects we contribute to """
    try:
        return contrib.data[username].keys()
    except KeyError:
        return []

## delcontrib function

def delcontrib(username, project):
    """ stop contributing to a project of someone else """
    if hascontrib(username, project):
        owner = getowner(username, project)
        delshare(owner, project, username)

## getsharelist function

def getsharelist(username, project):
    """ get users sharing a project with """
    if projects.data[username][project].has_key('share'):
        return projects.data[username][project]['share']
    else:
        return []

## addhours function

def addhours(owner, project, username, hours):
    """ add work hours to a project """

    if not projects.data[owner][project].has_key('hours'):
        projects.data[owner][project]['hours'] = {}
    if not projects.data[owner][project]['hours'].has_key(username):
        projects.data[owner][project]['hours'][username] = hours
    else:
        projects.data[owner][project]['hours'][username] += hours
    projects.save()
    return projects.data[owner][project]['hours'][username]

## gethourslist function

def gethourslist(username, project):
    """ get list of contributors and hours of work
        for a project
    """
    result = []
    if hasproject(username, project):
        owner = username
    elif hascontrib(username, project):
        owner = getowner(username, project)
    else:
        result.append("unknown project %s" % project)
        return result
    if projects.data[owner][project].has_key('hours'):
        # (h)ours(l)ist
        hl = projects.data[owner][project]['hours']
        if hl:
            # (c)ontributor to the project
            for c in hl:
                if hl[c] != 0:
                    result.append('%s (%s)' % (c, hl[c]))
    if not result:
        result.append("no work done yet")
    return result

## pt-add command

def handle_projectadd(bot, ievent):
    """ pt-add <projectname> <description> .. add project """
    if not ievent.rest:
        ievent.missing('<projectname> [<description>]')
        return
    username = users.getname(ievent.userhost)
    try:
        project, desc = ievent.rest.split(' ', 1)
    except ValueError:
        project = ievent.rest
        desc = None
    project = project.strip().lower()
    if hasproject(username, project) or hascontrib(username, project):
        ievent.reply('project %s already exists' % project)
        return
    if desc:
        desc = desc.strip()
    addproject(username, project, desc)
    ievent.reply('project %s added' % (project))

cmnds.add('pt-add', handle_projectadd, 'USER')
examples.add('pt-add', 'pt-add <projectname> [<description>] .. \
add a project to the project tracker', 'pt-add gc Gozerbot coden ;]')
tests.add('pt-add gozerbot coden', ' added|already')

## pt-list command

def handle_projectlist(bot, ievent):
    """ show available projects """
    username = users.getname(ievent.userhost)
    result = []
    l = getprojects(username)
    for project in l:
        desc = getdesc(username, project)
        if desc:
            result.append('%s (%s)' % (desc, project))
        else:
            result.append(project)
    l = getcontrib(username)
    for project in l:
        owner = getowner(username, project)
        desc = getdesc(owner, project)
        if desc:
            result.append('%s (%s, owner=%s)' % (desc, project, \
            owner))
        else:
            result.append('%s, owner=%s' % (project, owner))
    if result:
        ievent.reply('', result, dot=True)
    else:
        ievent.reply('no projects found')

cmnds.add('pt-list', handle_projectlist, 'USER')
examples.add('pt-list', 'list available projects', 'pt-list')
tests.add('pt-list', 'gozerbot')

## pt-del command

def handle_projectdel(bot, ievent):
    """ delete a project or stop contributing to it """
    try:
        project = ievent.rest.strip().lower()
    except ValueError:
        ievent.missing('<projectname>')
        return
    username = users.getname(ievent.userhost)
    if hasproject(username, project):
        delproject(username, project)
        ievent.reply('project %s deleted' % (project))
    elif hascontrib(username, project):
        delcontrib(username, project)
        ievent.reply('no longer contributing to %s' % (project))
    else:
        ievent.reply('no project %s' % project)

cmnds.add('pt-del', handle_projectdel, 'USER')
examples.add('pt-del', 'delete a project from the project tracker or \
stop contibuting to it', 'pt-del mekker')
tests.add('pt-del mekker')

## pt command

def handle_addhours(bot, ievent):
    """ add hours to a project """
    username = users.getname(ievent.userhost)
    try:
        project, rest = ievent.rest.split(' ', 1)
    except ValueError:
        ievent.missing('<projectname> <hours> [<comment>]')
        return
    project = project.strip().lower()

    if hasproject(username, project):
        owner = username
    elif hascontrib(username, project):
        owner = getowner(username, project)
    else:
        ievent.reply('no project %s' % project)
        return
    try:
        hours, comment = rest.split(' ', 1)
    except ValueError:
        hours = rest
        comment = ''
    try:
        hours = float(hours)
    except ValueError:
        ievent.reply('%s is not a number' % hours)
        return
    total = addhours(owner, project, username, hours)
    desc = getdesc(owner, project) or project
#    h = 'hours'
#    if abs(hours) == 1:
#        h = 'hour'
#    if hours < 0:
#        ievent.reply('removed %s %s from %s' % (hours, h, project))
#        return
#    ievent.reply('added %s %s to %s' % (hours, h, project))
     
    ievent.reply('hours spent on %s is now: %s' % (desc, total))

cmnds.add('pt', handle_addhours, 'USER')
examples.add('pt', 'track time spent on a project', 'pt gc 4')
tests.add('pt gozerbot coden 4')

## pt-report command

def handle_report(bot, ievent):
    """ display project report """
    username = users.getname(ievent.userhost)
    if not ievent.rest:
        result = []
        l = getprojects(username)
        for project in l:
            desc = getdesc(username, project) or project
            report = gethourslist(username, project)
            result.append('%s: %s' % (desc, ' '.join(report)))
        l = getcontrib(username)
        for project in l:
            owner = getowner(username, project)
            desc = getdesc(owner, project) or project
            report = gethourslist(owner, project)
            result.append('%s: %s' % (desc, ' '.join(report)))
        if result:
            ievent.reply('', result, dot=True)
        else:
            ievent.reply('no projects found')
    else:
        project = ievent.rest.strip().lower()
        if hasproject(username, project):
            owner = username
        elif hascontrib(username, project):
            owner = getowner(username, project)
        else:
            ievent.reply('no project %s' % project)
            return
        desc = getdesc(owner, project) or project
        report = gethourslist(owner, project)
        ievent.reply('%s: ' % desc, report, dot=True)

cmnds.add('pt-report', handle_report, 'USER')
examples.add('pt-report', 'report hours of work on a project', \
'1) pt-report 2) pt-report gc')
tests.add('pt-report gozerbot coden')

## pt-share command

def handle_addshare(bot, ievent):
    """ share a project with another user """
    username = users.getname(ievent.userhost)
    try:
        project, otheruser = ievent.rest.split(' ', 1)
    except ValueError:
        ievent.missing('<projectname> <user>')
        return
    project = project.strip().lower()
    if hascontrib(username, project):
        ievent.reply('only project owner can share a project')
        return
    if not hasproject(username, project):
        ievent.reply('no project %s' % project)
        return
    if not users.exist(otheruser):
        ievent.reply('unknown user %s' % otheruser)
        return
    if hasproject(otheruser, project) or \
    hascontrib(otheruser, project):
        ievent.reply('%s already has a project named %s' \
        % (otheruser, project))
        return
    addshare(username, project, otheruser)
    desc = getdesc(username, project) or project
    ievent.reply('sharing %s with %s' % (desc, otheruser))

cmnds.add('pt-share', handle_addshare, 'USER')
examples.add('pt-share', 'share a project with another user', \
'pt-share myproject knorrie')
tests.add('pt-share gozerbot coden dunker')

## pt-sharelist command

def handle_sharelist(bot, ievent):
    """ get a list of users we share a project with """
    username = users.getname(ievent.userhost)
    if not ievent.rest:
        ievent.missing('<projectname>')
        return
    project = ievent.rest.strip().lower()
    if hasproject(username, project):
        l = getsharelist(username, project)
        desc = getdesc(username, project) or project
	if not l:
	    ievent.reply('not sharing %s' % desc)
	else:
            ievent.reply('sharing %s with: ' % desc, l)
    elif hascontrib(username, project):
        owner = getowner(username, project)
        desc = getdesc(owner, project) or project
        l = getsharelist(owner, project)
        ievent.reply('owner is %s, sharing with: '\
        % owner, l)
    else:
        ievent.reply('no project %s' % project)

cmnds.add('pt-sharelist', handle_sharelist, 'USER')
examples.add('pt-sharelist', 'get a list of users we share a project \
with', 'pt-sharelist myproject')
tests.add('pt-sharelist gozerbot coden')

## pt-delshare command

def handle_delshare(bot, ievent):
    """ stop sharing a project with another user """
    username = users.getname(ievent.userhost)
    try:
        project, otheruser = ievent.rest.split(' ', 1)
    except ValueError:
        ievent.missing('<projectname> <user>')
        return
    project = project.strip().lower()
    if not hasproject(username, project):
        ievent.reply('no project %s' % project)
        return
    if not hascontrib(otheruser, project) or \
    getowner(otheruser, project) != username:
        ievent.reply('project not shared with %s' % otheruser)
        return
    delshare(username, project, otheruser)
    desc = getdesc(username, project) or project
    ievent.reply('stopped sharing %s with %s' % (desc, otheruser))

cmnds.add('pt-delshare', handle_delshare, 'USER')
examples.add('pt-delshare', 'stop sharing a project with another \
user', 'pt-delshare myproject somebody')
tests.add('pt-delshare gozerbot coden mekker')

#### BHJTW 23-01-2012
